2002 年 AJAX 诞生至今,前端从刀耕火种的年代,经历了一系列的发展,各种标准和工具百花齐放。下图中我们可以看到,自 2009 年 Node.js 诞生,前端先后出现了 CommonJS、AMD、CMD、UMD和ES Module 等模块规范,底层规范的发展催生出了一系列工具链的创新,比如 AMD 规范提出时社区诞生的模块加载工具requireJS,基于 CommonJS 规范的模块打包工具browserify,还有能让用户提前用上 ES Module 语法的 JS 编译器Babel、兼容各种模块规范的重量级打包工具Webpack以及基于浏览器原生 ES Module 支持而实现的 no-bundle 构建工具 Vite 等等。

总体而言,业界经历了一系列由规范、标准引领工程化改革的过程。构建工具作为前端工程化的核心要素,与底层的前端模块化规范和标准息息相关。接下来的时间,我就带你梳理一下前端模块化是如何演进的。这样你能更清楚地了解到各种模块化标准诞生的背景和意义,也能更好地理解 ES Module 为什么能够成为现今最主流的前端模块化标准。
# 无模块化标准阶段
早在模块化标准还没有诞生的时候,前端界已经产生了一些模块化的开发手段,如文件划分、命名空间和`IIFE 私有作用域。下面,我来简单介绍一下它们的实现以及背后存在的问题。
# 1. 文件划分
文件划分方式是最原始的模块化实现,简单来说就是将应用的状态和逻辑分散到不同的文件中,然后通过 HTML 中的 script 来一一引入。下面是一个通过文件划分实现模块化的具体例子:
// module-a.js
let data = "data";
// module-b.js
function method() {
console.log("execute method");
}
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./module-a.js"></script>
<script src="./module-b.js"></script>
<script>
console.log(data);
method();
</script>
</body>
</html>
从中可以看到module-a和module-b为两个不同的模块,通过两个 script 标签分别引入到 HTML 中,这么做看似是分散了不同模块的状态和运行逻辑,但实际上也隐藏着一些风险因素:
- 模块变量相当于在全局声明和定义,会有变量名冲突的问题。比如
module-b可能也存在data变量,这就会与module-a中的变量冲突。
